﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using RouterControl.Whs.SingletonService;
using System.IO;
using RouterControl.Service.DeviceSlaves;

namespace RouterControl.Service
{
    public class SingletonService2 : ISingletonService2
    {
        private readonly log4net.ILog _log = log4net.LogManager.GetLogger(typeof(SingletonService2));

        #region Misc

        public void Idle()
        {
            _log.Info("Idle.");
        }

        #endregion

        #region device search

        public List<RootDeviceInfo> GetFoundDevices()
        {
            try
            {
                var devs = RouterDeviceFinder.Instance.GetDevices();

                return devs.Select<RouterControl.IO.Device, RootDeviceInfo>(
                    o => new RootDeviceInfo((RouterControl.IO.RootDevice)o)
                    ).ToList();
            }
            catch (Exception e)
            {
                _log.Error("GetFoundDevices() failed", e);

                throw;
            }
        }

        public bool HasFoundDevices()
        {
            try
            {
                var devs = RouterDeviceFinder.Instance.GetDevices();

                return (devs.Count > 0);
            }
            catch (Exception e)
            {
                _log.Error("HasFoundDevices() failed", e);

                throw;
            }
        }

        public CommonInternetGateway.SummaryData? GetSelectedDeviceConfigData()
        {
            var cfg = RouterMaster.Instance.GetSlave<ConfigDataSlave>();

            if (cfg == null)
                return null;

            return cfg.Config;
        }

        public bool IsSearchingForDevices()
        {
            try
            {
                return RouterDeviceFinder.Instance.Searching;
            }
            catch (Exception e)
            {
                _log.Error("IsSearchingForDevices() failed", e);

                throw;
            }
        }

        public void SearchForDevices(bool waitUntilComplete)
        {
            RouterDeviceFinder.Instance.StartSearch();

            if (waitUntilComplete)
                RouterDeviceFinder.Instance.WaitUntilSearchComplete();
        }

        public void EnsureFirstSearchForDevices(bool waitUntilComplete)
        {
            if (waitUntilComplete)
                RouterDeviceFinder.Instance.EnsureFirstSearchAndWaitForLatest();
            else
                RouterDeviceFinder.Instance.EnsureFirstSearch();
        }

        public bool IsDeviceSelected()
        {
            try
            {
                return !string.IsNullOrEmpty(Properties.Settings.Default.SelectedRouterDeviceUniqueName);
            }
            catch (Exception e)
            {
                _log.Error("IsDeviceSelected() failed", e);

                throw;
            }
        }

        public bool IsSelectedDeviceFound()
        {
            try
            {
                return (RouterDeviceFinder.Instance.SelectedRouter != null);
            }
            catch (Exception e)
            {
                _log.Error("IsSelectedDeviceFound() failed", e);

                throw;
            }
        }

        public bool IsSelectedDeviceLost()
        {
            try
            {
                return RouterMaster.Instance.IsRouterLost;
            }
            catch (Exception e)
            {
                _log.Error("IsSelectedDeviceLost() failed", e);

                throw;
            }
        }

        public RootDeviceInfo? GetSelectedDevice()
        {
            try
            {
                var sd = RouterDeviceFinder.Instance.SelectedRouter;

                if (sd == null)
                    return null;

                return new RootDeviceInfo(sd);
            }
            catch (Exception e)
            {
                _log.Error("GetSelectedDevice() failed", e);

                throw;
            }
        }

        public string GetSelectedDeviceId()
        {
            try
            {
                return Properties.Settings.Default.SelectedRouterDeviceUniqueName;
            }
            catch (Exception e)
            {
                _log.Error("GetSelectedDeviceId() failed", e);

                throw;
            }
        }

        public void SetSelectedDeviceId(string devId)
        {
            SetSelectedDeviceId(devId, true);
        }

        public void SetSelectedDeviceId(string devId, bool save)
        {
            if (Properties.Settings.Default.SelectedRouterDeviceUniqueName == devId)
                return;

            try
            {
                // check for existing device
                if (!string.IsNullOrEmpty(devId))
                {
                    RouterDeviceFinder.Instance.EnsureFirstSearchAndWait();

                    var exists = RouterDeviceFinder.Instance.GetDeviceById(devId);

                    if (exists == null)
                        throw new Exception("device matching the defined id not found: " + devId);
                }

                // save
                Properties.Settings.Default.SelectedRouterDeviceUniqueName = devId;

                if (save)
                    Properties.Settings.Default.Save();
            }
            catch (Exception e)
            {
                _log.Error("SetSelectedDeviceId() failed", e);

                throw;
            }
        }

        #endregion

        #region traffic stats

        public bool IsTrafficStatsEnabled()
        {
            try
            {
                return Properties.Settings.Default.EnableTrafficStats;
            }
            catch (Exception e)
            {
                _log.Error("IsTrafficStatsEnabled() failed", e);

                throw;
            }
        }

        public void SetTrafficStatsEnabled(bool enable)
        {
            SetTrafficStatsEnabled(enable, true);
        }

        public void SetTrafficStatsEnabled(bool enable, bool save)
        {
            try
            {
                Properties.Settings.Default.EnableTrafficStats = enable;

                if (save)
                    Properties.Settings.Default.Save();
            }
            catch (Exception e)
            {
                _log.Error("SetTrafficStatsEnabled() failed", e);

                throw;
            }
        }

        #endregion

        #region router hijacking detection

        public bool IsRouterHijackingDetectionEnabled()
        {
            try
            {
                return Properties.Settings.Default.EnableRouterHijackingDetection;
            }
            catch (Exception e)
            {
                _log.Error("IsRouterHijackingDetectionEnabled() failed", e);

                throw;
            }
        }

        public void SetRouterHijackingDetectionEnabled(bool enable)
        {
            SetRouterHijackingDetectionEnabled(enable, true);
        }

        public void SetRouterHijackingDetectionEnabled(bool enable, bool save)
        {
            try
            {
                Properties.Settings.Default.EnableRouterHijackingDetection = enable;

                if (save)
                    Properties.Settings.Default.Save();
            }
            catch (Exception e)
            {
                _log.Error("SetRouterHijackingDetectionEnabled() failed", e);

                throw;
            }
        }

        #endregion

        #region port forwarding

        public ExtendedEnabled IsPortForwardingsMonitoringEnabled()
        {
            try
            {
                if (Properties.Settings.Default.EnablePortForwardingMonitoring)
                {
                    if (Properties.Settings.Default.EnablePortForwardingNotifications)
                    {
                        return ExtendedEnabled.EnabledWithNotifications;
                    }

                    return ExtendedEnabled.Enabled;
                }

                return ExtendedEnabled.Disabled;
            }
            catch (Exception e)
            {
                _log.Error("IsPortForwardingsMonitoringEnabled() failed", e);

                throw;
            }
        }

        public void SetPortForwardingsMonitoringEnabled(ExtendedEnabled enable)
        {
            SetPortForwardingsMonitoringEnabled(enable, true);
        }

        public void SetPortForwardingsMonitoringEnabled(ExtendedEnabled enable, bool save)
        {
            try
            {
                switch (enable)
                {
                    case ExtendedEnabled.Disabled:
                        Properties.Settings.Default.EnablePortForwardingMonitoring = false;
                        Properties.Settings.Default.EnablePortForwardingNotifications = true;   // reset to default
                        break;

                    case ExtendedEnabled.Enabled:
                        Properties.Settings.Default.EnablePortForwardingMonitoring = true;
                        Properties.Settings.Default.EnablePortForwardingNotifications = false;
                        break;

                    case ExtendedEnabled.EnabledWithNotifications:
                        Properties.Settings.Default.EnablePortForwardingMonitoring = true;
                        Properties.Settings.Default.EnablePortForwardingNotifications = true;
                        break;

                    default:
                        throw new Exception("unknown value: " + enable);
                }

                if (save)
                    Properties.Settings.Default.Save();
            }
            catch (Exception e)
            {
                _log.Error("SetPortForwardingsMonitoringEnabled() failed", e);

                throw;
            }
        }

        public CommonInternetGateway.PortForwarding[] GetPortForwardings(ApprovalStatus status)
        {
            try
            {
	            var cfg = RouterMaster.Instance.GetSlave<PortForwardingSlave>();
	
	            if (cfg == null)
	                return null;
	
	            var fwd = cfg.GetCurrentForwardings(status);
	
	            if (fwd == null)
	                return null;
	
	            // gather forwards
	            return fwd.ToArray();
            }
            catch (Exception ex)
            {
                _log.Error("GetPortForwardings() failed", ex);

                throw;
            }
        }

        public PortForwardingEx[] GetPortForwardingsEx()
        {
            try
            {
                var cfg = RouterMaster.Instance.GetSlave<PortForwardingSlave>();

                if (cfg == null)
                    return null;

                var fwd = cfg.GetCurrentForwardingsEx();

                if (fwd == null)
                    return null;

                // gather forwards
                return fwd.ToArray();
            }
            catch (Exception ex)
            {
                _log.Error("GetPortForwardings() failed", ex);

                throw;
            }
        }

        public void AddOrUpdatePortForwarding(CommonInternetGateway.PortForwarding fwd)
        {
            try
            {
	            var cfg = RouterMaster.Instance.GetSlave<PortForwardingSlave>();
	
	            if (cfg == null)
	                throw new Exception("device slave not found");
	
	            cfg.AddOrUpdateForwarding(fwd);
            }
            catch (Exception ex)
            {
                _log.Error("AddOrUpdatePortForwarding() failed", ex);

                throw;
            }
        }

        public void DeletePortForwarding(CommonInternetGateway.PortForwarding fwd)
        {
            try
            {
	            var cfg = RouterMaster.Instance.GetSlave<PortForwardingSlave>();
	
	            if (cfg == null)
	                throw new Exception("device slave not found");
	
	            cfg.DeleteForwarding(fwd);
            }
            catch (Exception ex)
            {
                _log.Error("DeletePortForwarding() failed", ex);

                throw;
            }
        }

        public void SetPortForwardingApproval(CommonInternetGateway.PortForwarding fwd, bool approve)
        {
            try
            {
                var cfg = RouterMaster.Instance.GetSlave<PortForwardingSlave>();

                if (cfg == null)
                    throw new Exception("device slave not found");

                if (approve)
                    cfg.ApproveForwarding(fwd.Id);
                else
                    cfg.DisapproveForwarding(fwd.Id);
            }
            catch (Exception ex)
            {
                _log.Error("SetPortForwardingApproval() failed", ex);

                throw;
            }
        }

        public void ClearAllPortForwardingApprovals()
        {
            try
            {
                var cfg = RouterMaster.Instance.GetSlave<PortForwardingSlave>();

                if (cfg == null)
                    throw new Exception("device slave not found");

                cfg.DisapproveAllForwardings();
            }
            catch (Exception ex)
            {
                _log.Error("ClearAllPortForwardingApprovals() failed", ex);

                throw;
            }
        }

        #endregion

        #region update checks

        public DateTime GetLastUpdateCheck()
        {
            try
            {
                return UpdateChecker.Instance.LastCheck;
            }
            catch (Exception e)
            {
                _log.Error("GetLastUpdateCheck() failed", e);

                throw;
            }
        }

        public Web.ReleaseInfo? GetNewerVersion()
        {
            try
            {
                return UpdateChecker.Instance.NewVersion;
            }
            catch (Exception e)
            {
                _log.Error("GetNewerVersion() failed", e);

                throw;
            }
        }

        public Web.ReleaseInfo? CheckNewerVersionNow()
        {
            try
            {
                var i = UpdateChecker.Instance;

                i.DoCheckManually();

                return i.NewVersion;
            }
            catch (Exception e)
            {
                _log.Error("CheckNewerVersionNow() failed", e);

                throw;
            }
        }

        #endregion

        #region schema upload

        public bool RequiresSelectedDeviceSchemaUpload()
        {
            try
            {
                var sd = RouterDeviceFinder.Instance.SelectedRouter;

                if (sd == null)
                    return false;

                using (var wsc = new Web.RouterControlWS2Factory())
                {
                    return !wsc.Channel.HasRouterDescriptionEx(sd.SchemaId, IO.DeviceSchema.SchemaVersion);
                }
            }
            catch (Exception e)
            {
                _log.Error("RequiresSelectedDeviceSchemaUpload() failed", e);

                throw;
            }
        }

        public bool UploadSelectedDeviceSchema()
        {
            try
            {
                var sd = RouterDeviceFinder.Instance.SelectedRouter;

                if (sd == null)
                    throw new Exception("no router selected");

                using (var wsc = new Web.RouterControlWS2Factory())
                {
                    // check for existing id
                    if (wsc.Channel.HasRouterDescriptionEx(sd.SchemaId, IO.DeviceSchema.SchemaVersion))
                    {
                        return false;
                    }

                    // upload schema
                    using (var ms = new System.IO.MemoryStream(64 * 1024 /* 64KB */))
                    {
                        sd.GetSchema().ToXml(ms);

                        ms.Position = 0;

                        wsc.Channel.UploadRouterDescription(ms);
                    }
                }

                return true;
            }
            catch (Exception e)
            {
                _log.Error("UploadSelectedDeviceSchema() failed", e);

                throw;
            }
        }

        public Stream GetSelectedDeviceSchema()
        {
            try
            {
                var sd = RouterDeviceFinder.Instance.SelectedRouter;

                if (sd == null)
                    throw new Exception("no router selected");

                // create stream
                var s = new MemoryStream(64 * 1024 /* 64KB */);

                sd.GetSchema().ToXml(s);

                s.Position = 0;

                return s;
            }
            catch (Exception e)
            {
                _log.Error("GetSelectedDeviceSchema() failed", e);

                throw;
            }
        }

        #endregion

        #region error reporting

        public bool IsErrorReportsSubmissionEnabled()
        {
            try
            {
                return Properties.Settings.Default.EnableErrorReportsSubmission2;
            }
            catch (Exception e)
            {
                _log.Error("IsErrorReportsSubmissionEnabled() failed", e);

                throw;
            }
        }

        public void SetErrorReportsSubmissionEnabled(bool enable)
        {
            SetErrorReportsSubmissionEnabled(enable, true);
        }

        public void SetErrorReportsSubmissionEnabled(bool enable, bool save)
        {
            try
            {
                Properties.Settings.Default.EnableErrorReportsSubmission2 = enable;

                if (save)
                    Properties.Settings.Default.Save();

                // enable or disable
                if (enable)
                    Whs.ErrorReporting.ErrorReportingSingleton.Instance.StartSubmittingErrors();
                else
                    Whs.ErrorReporting.ErrorReportingSingleton.Instance.StopSubmittingErrors(true);
            }
            catch (Exception e)
            {
                _log.Error("SetErrorReportsSubmissionEnabled() failed", e);

                throw;
            }
        }

        public ErrorReportingStatus GetErrorReportingStatus()
        {
            try
            {
                var eri = ErrorReporting.Instance;

                return new ErrorReportingStatus()
                {
                    IsSubmitting = eri.IsSubmitting,
                    Progress = eri.SubmittingProgress,
                    Submitted = eri.SubmittedErrors,
                    Unsubmitted = eri.UnsubmittedErrors
                };
            }
            catch (Exception e)
            {
                _log.Error("GetErrorReportingStatus() failed", e);

                throw;
            }
        }

        #endregion

        #region all settings

        public SettingsAll GetAllSettings()
        {
            try
            {
                return new SettingsAll()
                {
                    SelectedDeviceId = GetSelectedDeviceId(),
                    TrafficStatsEnabled = IsTrafficStatsEnabled(),
                    ErrorReportsSubmissionEnabled = IsErrorReportsSubmissionEnabled(),
                    RouterHijackingDetectionEnabled = IsRouterHijackingDetectionEnabled(),
                    PortForwardingsMonitoringEnabled = IsPortForwardingsMonitoringEnabled()
                };
            }
            catch (Exception e)
            {
                _log.Error("SetErrorReportsSubmissionEnabled() failed", e);

                throw;
            }
        }

        public void SetAllSettings(SettingsAll s)
        {
            try
            {
                SetSelectedDeviceId(s.SelectedDeviceId, false);
                SetTrafficStatsEnabled(s.TrafficStatsEnabled, false);
                SetErrorReportsSubmissionEnabled(s.ErrorReportsSubmissionEnabled, false);
                SetRouterHijackingDetectionEnabled(s.RouterHijackingDetectionEnabled, false);
                SetPortForwardingsMonitoringEnabled(s.PortForwardingsMonitoringEnabled, false);

                Properties.Settings.Default.Save();
            }
            catch (Exception e)
            {
                _log.Error("SetAllSettings() failed", e);

                throw;
            }
        }

        #endregion
    }
}
